package com.ruoyi.oss.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadResult;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.common.util.FileFormatVerifyUtil;
import com.common.util.RandomString;
import com.common.util.WithdrawalOfSurplus;
import com.msg.ResponseCode;
import com.returns.value.CodeAndData;
import com.returns.value.ICodeAndData;
import com.ruoyi.oss.config.OssConfig;
import com.ruoyi.oss.dto.OssCallbackParam;
import com.ruoyi.oss.dto.OssCallbackResult;
import com.ruoyi.oss.dto.OssPolicyResult;
import com.ruoyi.oss.service.OssService;


@Service
public class OssServiceImpl implements OssService {

	
	private OssConfig ossConfig;

	@Resource
	public void setOssConfig(OssConfig ossConfig) {
		this.ossConfig = ossConfig;
	}

	private static Map<String, String> IMAGE_TYPE = new HashMap<>();

	static {
		IMAGE_TYPE.put(".jpg", ".jpg");
		IMAGE_TYPE.put(".png", ".png");
		IMAGE_TYPE.put(".jpeg", ".jpeg");
		
		IMAGE_TYPE.put("image/png", "image/png");
		
		IMAGE_TYPE.put("image/jpeg", "image/jpeg");
	}

	private OSSClient getOssClient() {
		return new OSSClient(ossConfig.getEndpoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
	}

	public void init() {

	}

	/**
	 * 签名生成
	 */
	@Override
	public OssPolicyResult policy() {
		OssPolicyResult result = new OssPolicyResult();
		// 存储目录
//		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
//		String dir = ossConfig.getPicPrefix() + sdf.format(new Date());
//		// 签名有效期
//		long expireEndTime = System.currentTimeMillis() + ossConfig.getExpire() * 1000;
//		Date expiration = new Date(expireEndTime);
//		// 文件大小
//		long maxSize = ossConfig.getSize();
//		// 回调
//		OssCallbackParam callback = new OssCallbackParam();
//		callback.setCallbackUrl(ossConfig.getCallback());
//		callback.setCallbackBody(
//				"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
//		callback.setCallbackBodyType("application/x-www-form-urlencoded");
//		// 提交节点
//		String action = "http://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint();
//
//		JSONObject jsonObject = new JSONObject();
//		
//		OSSClient ossClient = getOssClient();
//		try {
//			PolicyConditions policyConds = new PolicyConditions();
//			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
//			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
//
//			String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
//			byte[] binaryData = postPolicy.getBytes("utf-8");
//			String policy = BinaryUtil.toBase64String(binaryData);
//			String signature = ossClient.calculatePostSignature(postPolicy);
//			String callbackData = BinaryUtil.toBase64String(jsonObject.app(callback).toString().getBytes("utf-8"));
//			// 返回结果
//			result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
//			result.setPolicy(policy);
//			result.setSignature(signature);
//			result.setDir(dir);
//			result.setCallback(callbackData);
//			result.setHost(action);
//		} catch (Exception e) {
//			e.printStackTrace()
//		} finally {
//			ossClient.shutdown();
//		}
		return result;
	}

	@Override
	public OssCallbackResult callback(HttpServletRequest request) {
		OssCallbackResult result = new OssCallbackResult();
		String filename = request.getParameter("filename");
		filename = "http://".concat(ossConfig.getBucketName()).concat(".").concat(ossConfig.getEndpoint()).concat("/")
				.concat(filename);
		result.setFilename(filename);
		result.setSize(request.getParameter("size"));
		result.setMimeType(request.getParameter("mimeType"));
		result.setWidth(request.getParameter("width"));
		result.setHeight(request.getParameter("height"));
		return result;
	}

	
	public <V> ICodeAndData<Map<String, Object>> signURL(String fileName, String type, Function<V, String> path) {
		

//        String typeValue = IMAGE_TYPE.get(type);
//        
//        if (null == typeValue) {
//        	return  new CodeAndData<Map<String, Object>>(ResponseCode.ERROR_FILE_TYPE, null);
//        }
		
		 // 以华东1（杭州）的外网Endpoint为例，其它Region请按实际情况填写。
        // 从环境变量中获取访问凭证。运行本代码示例之前，请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory
        		.newDefaultCredentialProvider(
        				ossConfig.getAccessKeyId(),
        				ossConfig.getAccessKeySecret()
        				);
        // 填写Bucket名称，例如examplebucket。
        String bucketName = this.ossConfig.getBucketName();
        // 填写Object完整路径，例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = getDir(path, fileName, fileName.substring(fileName.indexOf('.')));
        // 填写本地文件的完整路径。如果未指定本地路径，则默认从示例程序所属项目对应本地路径中上传文件。

        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder()
        		.build(ossConfig.getEndpoint(), credentialsProvider);
        
        // 设置请求头。
        Map<String, String> headers = new HashMap<String, String>();
        /*// 指定Object的存储类型。
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        // 指定ContentType。
        ;*/

        
        String value = "multipart/form-data";
		headers.put(OSSHeaders.CONTENT_TYPE, value);
        
        // 设置用户自定义元信息。
        Map<String, String> userMetadata = new HashMap<String, String>();
        /*userMetadata.put("key1","value1");
        userMetadata.put("key2","value2");*/

        URL signedUrl = null;
        try {
            // 指定生成的签名URL过期时间，单位为毫秒。本示例以设置过期时间为1小时为例。
            Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

            // 生成签名URL。
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
            // 设置过期时间。
            request.setExpiration(expiration);

            // 将请求头加入到request中。
            request.setHeaders(headers);
            // 添加用户自定义元信息。
//            request.setUserMetadata(userMetadata);

            // 通过HTTP PUT请求生成签名URL。
            signedUrl = ossClient.generatePresignedUrl(request);
            // 打印签名URL。
            System.out.println("signed url for putObject: " + signedUrl);
            
            Map<String, Object> map = new HashMap<String, Object>();
            
            
            
            map.put("url", signedUrl.toString());
            
            map.put("expiration", expiration);
            
            map.put("type", value);
            
			map.put("objectName", /* "/" + bucketName+ "/"+ */ objectName);
            
            return new CodeAndData<Map<String, Object>>(ResponseCode.SUCCESS, map);

        } catch (Exception oe) {
            String string = "Caught an OSSException, which means your request made it to OSS,but was rejected with an error response for some reason.";
			System.out.println(string);
            
            return new CodeAndData<Map<String, Object>>(string, ResponseCode.ERROR.getCode());
        } 
	}
	
	private <V> String getDir(Function<V, String> function, String originalFileName, String suffix) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

		Date date = new Date();

		String perfix = function.apply((V) ossConfig);

		String dir = perfix + sdf.format(date);

		RandomString randomString = new RandomString(5);
		
		String fileName = originalFileName.substring(0, originalFileName.lastIndexOf("."));
		

//		originalFileName = fileName + randomString.getString() + suffix;
//		originalFileName = fileName + randomString.getString() + suffix;

		return dir + "/" + originalFileName;
	}
	
	@Override
	public <V> ICodeAndData<String> uploadFileNFT(Function<V, String> function, MultipartFile file) {

		InputStream inputStream = null;

		OSSClient ossClient = getOssClient();

		try {

			// 获取文件名称
			String fileName = file.getOriginalFilename();

			String suffix = fileName.substring(fileName.indexOf('.'));

			if (null == IMAGE_TYPE.get(suffix)) {
				return new CodeAndData<String>(ResponseCode.ERROR_FILE_TYPE, null);
			}
			

			// 创建OSS实例。
			// 获取上传文件的输入流
			inputStream = file.getInputStream();
			
			boolean pass = FileFormatVerifyUtil.suffixVerify(inputStream, suffix);
//			
			if (false == pass) {
				return new CodeAndData<String>(ResponseCode.ERROR_FILE_TYPE, null);
			}
			


			String bucketName = this.ossConfig.getBucketName();

			String dir = getDir(function, fileName, suffix);

			long totalSize = file.getSize();

			Field pathField = inputStream.getClass().getDeclaredField("path");

			pathField.setAccessible(true);

			String path = (String) pathField.get(inputStream);

			File cacheFile = new File(path);

			long partSize = this.ossConfig.getSize();

			// 创建InitiateMultipartUploadRequest对象。
			InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, dir);

			// 初始化分片。
			InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);

			String uploadId = upresult.getUploadId();

			// 总共分多少个
			long count = WithdrawalOfSurplus.ofSurplus(totalSize, partSize);

			long startPos = 0, curPartSize = 0;

			UploadPartRequest uploadPartRequest;

			// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
			List<PartETag> partETags = new ArrayList<PartETag>();

			for (int i = 0; i < count; i++) {

				startPos = i * partSize;
				
				inputStream = new FileInputStream(cacheFile);

				curPartSize = (i + 1 == count) ? (totalSize - startPos) : partSize;

				// 跳过已经上传的分片。
				inputStream.skip(startPos);
				uploadPartRequest = new UploadPartRequest();
				uploadPartRequest.setBucketName(bucketName);
				uploadPartRequest.setKey(dir);
				uploadPartRequest.setUploadId(uploadId);
				uploadPartRequest.setInputStream(inputStream);
				// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
				uploadPartRequest.setPartSize(curPartSize);
				// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
				uploadPartRequest.setPartNumber(i + 1);
				// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
				UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
				// 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
				partETags.add(uploadPartResult.getPartETag());

			}
			// 创建CompleteMultipartUploadRequest对象。
			// 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
			CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
					bucketName, dir, uploadId, partETags);

			// 如果需要在完成分片上传的同时设置文件访问权限，请参考以下示例代码。
			// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
			// 指定是否列举当前UploadId已上传的所有Part。如果通过服务端List分片数据来合并完整文件时，以上CompleteMultipartUploadRequest中的partETags可为null。
			// Map<String, String> headers = new HashMap<String, String>();
			// 如果指定了x-oss-complete-all:yes，则OSS会列举当前UploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
			// 如果指定了x-oss-complete-all:yes，则不允许继续指定body，否则报错。
			// headers.put("x-oss-complete-all","yes");
			// completeMultipartUploadRequest.setHeaders(headers);

			// 完成分片上传。
			CompleteMultipartUploadResult completeMultipartUploadResult = ossClient
					.completeMultipartUpload(completeMultipartUploadRequest);
			System.out.println(completeMultipartUploadResult.getETag());

			// 调用oss实例中的方法实现上传
			// 参数1： Bucket名称
			// 参数2： 上传到oss文件路径和文件名称 /aa/bb/1.jpg
			// 参数3： 上传文件的输入流
//            ossClient.putObject(ALIYUN_OSS_BUCKET_NAME, dir, inputStream);

			// 把上传后文件路径返回
			// 需要把上传到阿里云oss路径手动拼接出来
			// https://achang-edu.oss-cn-hangzhou.aliyuncs.com/default.gif
			String url = "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/" + dir;
			return new CodeAndData<String>(ResponseCode.SUCCESS, url);
		} catch (Exception e) {

			e.printStackTrace();
			return new CodeAndData<String>(ResponseCode.ERROR, e.getMessage());
		} finally {
			// 关闭OSSClient。
			ossClient.shutdown();
		}
	}

	@Override
	public Integer deleteFileToOSS(List<String> fileItems) {
		if (0 == fileItems.size()) {
			return 1;
		}

		OSSClient obsClient = getOssClient();

		String bucketName = ossConfig.getBucketName();

		String res;

		String replace = "https://" + ossConfig.getBucketName() + "." + ossConfig.getEndpoint() + "/";

		for (String string : fileItems) {

			res = string.replace(replace, "");

			obsClient.deleteObject(bucketName, res);
		}
		
		obsClient.shutdown();
		return 1;
	}

}
